#include "panda_log.hpp"

#include <cassert>

#include <stdexcept>
#include <thread>

#include "panda_utils.hpp"
#include "uv.h"

#ifdef DEBUG
#ifdef PANDA_WIN32
#include <windows.h>
#elif PANDA_MAC
#include <CoreServices/CoreServices.h>
#elif PANDA_ANDROID
#include <android/log.h>
static int kMaxLogLineSize = 1024 - 60;
#endif
#endif


using namespace Panda;

void DebugLogToCout(const char* l) { std::cout << l; }

const char* LogHandler::LogLevelToStr(LogLevel l) {
  switch (l) {
    case LogLevel::Debug: {
      return "Debug";
    }
    case LogLevel::Info: {
      return "Info";
    }
    case LogLevel::Warning: {
      return "Warning";
    }
    case LogLevel::Error: {
      return "Error";
    }
    case LogLevel::Fatal: {
      return "Fatal";
    }
    case LogLevel::None: {
      return "None";
    }
    default:
      return "Unknow";
  }
}

LogHandler& LogHandler::Instance() {
  static LogHandler* instatce = new LogHandler();
  return *instatce;
}

void LogHandler::SetLogHandler(std::function<void(const char*)>& handler) {
  this->handler = handler;
}

void LogHandler::SetLogLevel(LogLevel l) { level = l; }

void LogHandler::SetWriteFileName(bool w) { writeFile = w; }

void LogHandler::SetWriteFunction(bool w) { writeFunction = w; }

void LogHandler::SetWriteLine(bool w) { writeLine = w; }

LogHandler::LogHandler() {
  handler = DebugLogToCout;
#ifdef DEBUG
#else
  level = LogHandler::Info;
#endif
}

LogItem::LogItem(LogHandler::LogLevel l, const char* file, const char* func,
                 size_t line) {
  level = l;
  if (level >= LogHandler::Instance().level) {
    buff << Time::NowStr() << " ";
    if (LogHandler::Instance().writeProcessId) {
      buff << "[" << uv_os_getpid() << "]";
    }
    if (LogHandler::Instance().writeThreadId) {
      auto tId = std::this_thread::get_id();
      buff << "[" << *((int*)&tId) << "]";
    }

    if (LogHandler::Instance().writeLogLevel) {
      buff << "[" << LogHandler::LogLevelToStr(l) << "]";
    }
    if (LogHandler::Instance().writeFile) {
      buff << "[" << file;
      if (LogHandler::Instance().writeLine) {
        buff << ":" << line;
      }
      buff << "]";
    }
    if (LogHandler::Instance().writeFunction) {
      buff << "[" << func << "]";
    }
  }
}

LogItem::~LogItem() {
  if (level >= LogHandler::Instance().level) {
#ifdef PANDA_WIN32
      buff << "\r\n";
#else
      buff << "\n";
#endif
    if (LogHandler::Instance().handler)
      LogHandler::Instance().handler(buff.str().c_str());
#ifdef DEBUG
#ifdef PANDA_WIN32
    OutputDebugStringA(buff.str().c_str());
#elif PANDA_MAC
#elif PANDA_ANDROID
  int prio;
  switch (level) {
    case LogLevel::Debug: {
      prio = ANDROID_LOG_VERBOSE
    }
    case LogLevel::Info: {
      prio = ANDROID_LOG_INFO;
    }
    case LogLevel::Warning: {
      prio = ANDROID_LOG_WARN;
    }
    case LogLevel::Error:
    case LogLevel::Fatal:  {
      prio = ANDROID_LOG_ERROR;
    }
    default:
      prio = ANDROID_LOG_UNKNOWN;
  }

  int size = str.size();
  int line = 0;
  int idx = 0;
  const int max_lines = size / kMaxLogLineSize + 1;
  if (max_lines == 1) {
    __android_log_print(prio, "Panda", "%.*s", size, str.c_str());
  } else {
    while (size > 0) {
      const int len = std::min(size, kMaxLogLineSize);
      __android_log_print(prio, tag, "[%d/%d] %.*s", line + 1, max_lines, len,
                          str.c_str() + idx);
      idx += len;
      size -= len;
      ++line;
    }
  }
#endif
#endif
    if (level == LogHandler::LogLevel::Fatal) {
#ifdef DEBUG
      assert(0);
#elif FatalThrowException
      throw std::runtime_error(buff.str());
#endif  // DEBUG
    }
  }
}
